🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / takserver / src / commonMain / kotlin / org / meshtastic / core / takserver / CoTXmlParser.kt
Displaying Raw • Download
core/takserver/src/commonMain/kotlin/org/meshtastic/core/takserver/CoTXmlParser.kt bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 6.38 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72package T7ee787org.meshtastic.core.takserver
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787nl.adaptivity.xmlutil.serialization.XML
Tff7b72import T7ee787kotlin.time.Clock
Tff7b72import T7ee787kotlin.time.Instant
T8b949e// XML.compat preserves xmlutil's pre-1.0 serialization defaults. The non-deprecated 1.0 builders
T8b949e// (XML.recommended/XML.V1) intentionally change serialization defaults, which would alter the CoT XML
T8b949e// wire format we exchange with ATAK/TAK servers. Staying on the compat policy until that migration can
T8b949e// be validated against real TAK interop; suppress the soft-deprecation on the factory itself.
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffDEPRECATIONTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3xmlParser Tff7b72=
Te6edf3XMLTb4b4b4.Te6edf3compat Tb4b4b4{
T8b949e// xmlutil 1.0.0 moved repairNamespaces from the policy builder to the top-level XML config.
Te6edf3repairNamespaces Tff7b72= Tff7b72false
Te6edf3defaultPolicy Tb4b4b4{ Te6edf3ignoreUnknownChildrenTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72class T56d364CoTXmlParserTb4b4b4(Tff7b72private Tff7b72val Te6edf3xmlTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8ffparseTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Te6edf3CoTMessageTff7b72> Tff7b72= Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3event Tff7b72= Te6edf3xmlParserTb4b4b4.Te6edf3decodeFromStringTb4b4b4(Te6edf3CoTEventXmlTb4b4b4.Te6edf3serializerTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3xmlTb4b4b4)
Te6edf3ResultTb4b4b4.Te6edf3successTb4b4b4(Te6edf3buildCoTMessageTb4b4b4(Te6edf3eventTb4b4b4)Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IllegalArgumentExceptionTb4b4b4) Tb4b4b4{
Te6edf3ResultTb4b4b4.Te6edf3failureTb4b4b4(Te6edf3eTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3kotlinxTb4b4b4.Te6edf3serializationTb4b4b4.Te6edf3SerializationExceptionTb4b4b4) Tb4b4b4{
Te6edf3ResultTb4b4b4.Te6edf3failureTb4b4b4(Te6edf3eTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3nlTb4b4b4.Te6edf3adaptivityTb4b4b4.Te6edf3xmlutilTb4b4b4.Te6edf3XmlExceptionTb4b4b4) Tb4b4b4{
Te6edf3ResultTb4b4b4.Te6edf3failureTb4b4b4(Te6edf3eTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildCoTMessageTb4b4b4(Te6edf3eventTb4b4b4: Te6edf3CoTEventXmlTb4b4b4)Tb4b4b4: Te6edf3CoTMessage Tb4b4b4{
Tff7b72val Te6edf3detail Tff7b72= Te6edf3eventTb4b4b4.Te6edf3detail
Tff7b72return Te6edf3CoTMessageTb4b4b4(
Te6edf3uid Tff7b72= Te6edf3eventTb4b4b4.Te6edf3uidTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Ta5d6ff"Ta5d6fftak-0Ta5d6ff" Tb4b4b4}Tb4b4b4,
Te6edf3type Tff7b72= Te6edf3eventTb4b4b4.Te6edf3typeTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Ta5d6ff"Ta5d6ffa-f-G-U-CTa5d6ff" Tb4b4b4}Tb4b4b4,
Te6edf3time Tff7b72= Te6edf3parseDateTb4b4b4(Te6edf3eventTb4b4b4.Te6edf3timeTb4b4b4)Tb4b4b4,
Te6edf3start Tff7b72= Te6edf3parseDateTb4b4b4(Te6edf3eventTb4b4b4.Te6edf3startTb4b4b4)Tb4b4b4,
Te6edf3stale Tff7b72= Te6edf3parseDateTb4b4b4(Te6edf3eventTb4b4b4.Te6edf3staleTb4b4b4)Tb4b4b4,
Te6edf3how Tff7b72= Te6edf3eventTb4b4b4.Te6edf3howTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Ta5d6ff"Ta5d6ffm-gTa5d6ff" Tb4b4b4}Tb4b4b4,
Te6edf3latitude Tff7b72= Te6edf3eventTb4b4b4.Te6edf3pointTb4b4b4.Te6edf3latTb4b4b4,
Te6edf3longitude Tff7b72= Te6edf3eventTb4b4b4.Te6edf3pointTb4b4b4.Te6edf3lonTb4b4b4,
Te6edf3hae Tff7b72= Te6edf3eventTb4b4b4.Te6edf3pointTb4b4b4.Te6edf3haeTb4b4b4,
Te6edf3ce Tff7b72= Te6edf3eventTb4b4b4.Te6edf3pointTb4b4b4.Te6edf3ceTb4b4b4,
Te6edf3le Tff7b72= Te6edf3eventTb4b4b4.Te6edf3pointTb4b4b4.Te6edf3leTb4b4b4,
Te6edf3contact Tff7b72= Te6edf3buildContactTb4b4b4(Te6edf3detailTb4b4b4)Tb4b4b4,
Te6edf3group Tff7b72= Te6edf3buildGroupTb4b4b4(Te6edf3detailTb4b4b4)Tb4b4b4,
Te6edf3status Tff7b72= Te6edf3detailTff7b72?.Te6edf3statusTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3CoTStatusTb4b4b4(Te6edf3battery Tff7b72= Tffa657itTb4b4b4.Te6edf3batteryTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3track Tff7b72= Te6edf3detailTff7b72?.Te6edf3trackTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3CoTTrackTb4b4b4(Te6edf3speed Tff7b72= Tffa657itTb4b4b4.Te6edf3speedTb4b4b4, Te6edf3course Tff7b72= Tffa657itTb4b4b4.Te6edf3courseTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3chat Tff7b72= Te6edf3buildChatTb4b4b4(Te6edf3detailTb4b4b4)Tb4b4b4,
Te6edf3remarks Tff7b72= Te6edf3buildRemarksTb4b4b4(Te6edf3detailTb4b4b4)Tb4b4b4,
T8b949e// Stripped version used as the raw_detail protobuf payload: drops bloat
T8b949e// elements (colors, icons, archives, shapes, etc.) so unmapped CoT types
T8b949e// have any chance of fitting in a LoRa mesh packet. See [CoTDetailStripper].
Te6edf3parsedDetailXml Tff7b72= Te6edf3extractDetailInnerXmlTb4b4b4(Te6edf3xmlTb4b4b4)Tff7b72?.Te6edf3letTb4b4b4(Te6edf3CoTDetailStripperTff7b72::Te6edf3stripTb4b4b4)Tb4b4b4,
T8b949e// Verbatim original event XML kept for diagnostic logging only — never
T8b949e// goes on the wire.
Te6edf3sourceEventXml Tff7b72= Te6edf3xmlTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Extract the exact content between `<detail>` and `</detail>` from the original XML string. Used as the
* `raw_detail` fallback payload when we can't map the CoT type to a structured [org.meshtastic.proto.TAKPacketV2]
* payload. Preserves any extension elements the xmlutil parser discarded as "unknown children".
*
* Returns null for self-closed `<detail/>` or when no detail element is present.
*/
Tff7b72private Tff7b72fun Td2a8ffextractDetailInnerXmlTb4b4b4(Te6edf3xmlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tb4b4b4{
T8b949e// Match `<detail ...>` (not `<detail/>`) through its matching close tag.
Tff7b72val Te6edf3openIdx Tff7b72= Te6edf3xmlTb4b4b4.Te6edf3indexOfTb4b4b4(Ta5d6ff"Ta5d6ff<detailTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3openIdx Tff7b72< T79c0ff0Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3openEnd Tff7b72= Te6edf3xmlTb4b4b4.Te6edf3indexOfTb4b4b4(Ta5d6ff'>'Tb4b4b4, Te6edf3openIdxTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3openEnd Tff7b72< T79c0ff0Tb4b4b4) Tff7b72return Tff7b72null
T8b949e// Self-closed tag like `<detail/>` has no content.
Tff7b72if Tb4b4b4(Te6edf3xmlTff7b72[Te6edf3openEnd Tff7b72- T79c0ff1Tff7b72] Tff7b72=Tff7b72= Ta5d6ff'/'Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3closeIdx Tff7b72= Te6edf3xmlTb4b4b4.Te6edf3indexOfTb4b4b4(Ta5d6ff"Ta5d6ff</detail>Ta5d6ff"Tb4b4b4, Te6edf3openEndTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3closeIdx Tff7b72< T79c0ff0Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3inner Tff7b72= Te6edf3xmlTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3openEnd Tff7b72+ T79c0ff1Tb4b4b4, Te6edf3closeIdxTb4b4b4)Tb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3innerTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Tff7b72null Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildContactTb4b4b4(Te6edf3detailTb4b4b4: Te6edf3CoTDetailXml?Tb4b4b4)Tb4b4b4: Te6edf3CoTContact? Tff7b72= Te6edf3detailTff7b72?.Te6edf3contactTff7b72?.Te6edf3let Tb4b4b4{
Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3callsignTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3endpoint Tff7b72!Tff7b72= Tff7b72null Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3phone Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3CoTContactTb4b4b4(Te6edf3callsign Tff7b72= Tffa657itTb4b4b4.Te6edf3callsignTb4b4b4, Te6edf3endpoint Tff7b72= Tffa657itTb4b4b4.Te6edf3endpointTb4b4b4, Te6edf3phone Tff7b72= Tffa657itTb4b4b4.Te6edf3phoneTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildGroupTb4b4b4(Te6edf3detailTb4b4b4: Te6edf3CoTDetailXml?Tb4b4b4)Tb4b4b4: Te6edf3CoTGroup? Tff7b72= Te6edf3detailTff7b72?.Te6edf3groupTff7b72?.Te6edf3let Tb4b4b4{
Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3roleTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3CoTGroupTb4b4b4(
Te6edf3name Tff7b72= Tffa657itTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Te6edf3DEFAULT_TAK_TEAM_NAME Tb4b4b4}Tb4b4b4,
Te6edf3role Tff7b72= Tffa657itTb4b4b4.Te6edf3roleTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Te6edf3DEFAULT_TAK_ROLE_NAME Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildChatTb4b4b4(Te6edf3detailTb4b4b4: Te6edf3CoTDetailXml?Tb4b4b4)Tb4b4b4: Te6edf3CoTChat? Tff7b72= Te6edf3detailTff7b72?.Te6edf3chatTff7b72?.Te6edf3let Tb4b4b4{
Tff7b72val Te6edf3remarksText Tff7b72= Te6edf3detailTb4b4b4.Te6edf3remarksTff7b72?.Te6edf3value Tff7b72?: Ta5d6ff"Ta5d6ff"
Te6edf3CoTChatTb4b4b4(
Te6edf3message Tff7b72= Te6edf3remarksTextTb4b4b4,
Te6edf3senderCallsign Tff7b72= Tffa657itTb4b4b4.Te6edf3senderCallsignTb4b4b4,
Te6edf3chatroom Tff7b72= Tffa657itTb4b4b4.Te6edf3chatroomTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72?: Ta5d6ff"Ta5d6ffAll Chat RoomsTa5d6ff" Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildRemarksTb4b4b4(Te6edf3detailTb4b4b4: Te6edf3CoTDetailXml?Tb4b4b4)Tb4b4b4: Tffa657String? Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3detailTff7b72?.Te6edf3chat Tff7b72=Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3detailTff7b72?.Te6edf3remarks Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3detailTb4b4b4.Te6edf3remarksTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3detailTb4b4b4.Te6edf3remarksTb4b4b4.Te6edf3value
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffparseDateTb4b4b4(Te6edf3dateStringTb4b4b4: Tffa657String?Tb4b4b4)Tb4b4b4: Te6edf3Instant Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3dateStringTb4b4b4.Te6edf3isNullOrEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Te6edf3ClockTb4b4b4.Te6edf3SystemTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72try Tb4b4b4{
Te6edf3InstantTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3dateStringTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredTb4b4b4: Te6edf3IllegalArgumentExceptionTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3cleaned Tff7b72= Te6edf3dateStringTb4b4b4.Te6edf3replaceTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"""Ta5d6ff\Ta5d6ff.Ta5d6ff\Ta5d6ffd+Ta5d6ff"""Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ffZTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff+00:00Ta5d6ff"Tb4b4b4)
Te6edf3InstantTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3cleanedTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredInnerTb4b4b4: Te6edf3IllegalArgumentExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffUnparseable CoT date 'Tffd700$Te6edf3dateStringTa5d6ff', falling back to now()Ta5d6ff" Tb4b4b4}
Te6edf3ClockTb4b4b4.Te6edf3SystemTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.14s